<?php


namespace EchoPHP\Support;


use ArrayAccess;
use Countable;
use IteratorAggregate;
use ArrayIterator;
use JsonSerializable;
use Traversable;
use EchoPHP\Support\Traits\Macroable;
use EchoPHP\Support\Contacts\Arrayable;
use EchoPHP\Support\Contacts\Jsonable;

class Collection implements ArrayAccess, Arrayable, Countable, IteratorAggregate, Jsonable, JsonSerializable {

    use Macroable;

    protected $items = [];

    protected static $proxies = [
        'average', 'avg', 'contains', 'each', 'every', 'filter', 'first', 'flatMap', 'groupBy', 'keyBy',
        'map', 'max', 'min', 'partition', 'reject', 'some', 'sortBy', 'sortByDesc', 'sum', 'unique'
    ];

    public function __construct($items = []) {
        $this->items = $this->getArrayableItems($items);
    }

    /**
     * 如果有多个集合，通过make方法创建新的集合
     * @param array $items
     * @return Collection
     */
    public static function make($items = []) {
        return new static($items);
    }

    /**
     * 生成指定数据元素的集合
     * @param $number
     * @param callable|null $callback
     * @return Collection
     */
    public static function times($number, callable $callback = null) {
        if ($number < 1) {
            return new static();
        }
        if (is_null($callback)) {
            return new static(range(1, $number));
        }
        return (new static(range(1, $number)))->map($callback);
    }

    /**
     * 将一个数值包括成集合，如果已经是集合类直接用于创建新的集合
     * @param $value
     * @return Collection
     */
    public static function wrap($value) {
        return $value instanceof self ? new static($value) : new static(Arr::wrap($value));
    }

    /**
     * 将集合拆分为具体数据
     * @param $value
     * @return array|mixed
     */
    public static function unwrap($value) {
        return $value instanceof self ? $value->all() : $value;
    }

    /**
     * 在集合每一个元素上执行映射
     * @param callable $callback
     * @return Collection
     */
    public function map(callable $callback) {
        $keys = array_keys($this->items);
        $items = array_map($callback, $this->items, $keys);
        return new static(array_combine($keys, $items));
    }

    /**
     * 获取集合的全部元素
     * @return array|mixed
     */
    public function all() {
        return $this->items;
    }

    /**
     * 利用回调函数计算集合总和
     * @param null $callback
     * @return float|int|mixed
     */
    public function sum($callback = null) {
        if (is_null($callback)) {
            return array_sum($this->items);
        }

        $callback = $this->valueRetriever($callback);

        return $this->reduce(function ($result, $item) use ($callback) {
            return $result + $callback($item);
        });
    }

    /**
     * 从集合中删除元素
     * @param $offset
     * @param null $length
     * @param array $replacement
     * @return Collection
     */
    public function splice($offset, $length = null, $replacement = []) {
        if (func_num_args() == 1) {
            return new static(array_splice($this->items, $offset));
        }

        return new static(array_splice($this->items, $offset, $length, $replacement));
    }

    /**
     * 使用回调函数将集合迭代为单一值
     * @param callable $callback
     * @param null $initial
     * @return mixed
     */
    public function reduce(callable $callback, $initial = null) {
        return array_reduce($this->items, $callback, $initial);
    }

    /**
     * 对集合元素进行排序
     * @param bool $desc 升序或者降序
     * @param callable|null $callback
     * @return Collection
     */
    public function sort($desc = false, callable $callback = null) {
        $items = $this->items;
        $callback ? uasort($items, $callback) : ($desc ? arsort($items) : asort($items));
        return new static($items);
    }

    /**
     * 使用回调对集合进行排序
     * @param $callback
     * @param int $options
     * @param bool $desc
     * @return Collection
     */
    public function sortBy($callback, $options = SORT_REGULAR, $desc = false) {
        $results = [];

        $callback = $this->valueRetriever($callback);
        foreach ($this->items as $key => $value) {
            $results[$key] = $callback($value, $key);
        }
        $desc ? arsort($results, $options) : asort($results, $options);
        foreach (array_keys($results) as $key) {
            $results[$key] = $this->items[$key];
        }

        return new static($results);
    }

    /**
     * 使用回调对集合进行降序排序
     * @param $callback
     * @param int $options
     * @return Collection
     */
    public function sortByDesc($callback, $options = SORT_REGULAR) {
        return $this->sortBy($callback, $options, true);
    }

    /**
     * 对键名进行排序
     * @param int $options
     * @param bool $desc
     * @return Collection
     */
    public function sortKeys($options = SORT_REGULAR, $desc = false) {
        $item = $this->items;
        $desc ? kssort($item, $options) : ksort($item, $options);

        return new static($item);
    }

    /**
     * 对键名进行降序排序
     * @param int $options
     * @return Collection
     */
    public function sortKeysDesc($options = SORT_REGULAR) {
        return $this->sortKeys($options, true);
    }

    /**
     * 数值查找
     * @param $value
     * @return \Closure
     */
    protected function valueRetriever($value) {
        if ($this->useAsCallable($value)) {
            return $value;
        }
        return function ($item) use ($value) {
            return static::dataGet($item, $value);
        };
    }

    /**
     * 从目标数据中获取键值
     * @param $target
     * @param $key
     * @param null $default
     * @return array|mixed
     */
    protected function dataGet($target, $key, $default = null) {
        if (is_null($key)) {
            return $target;
        }

        $key = is_array($key) ? $key : explode('.', $key);

        while (!is_null($segment = array_shift($key))) {
            if ('*' === $segment) {
                if ($target instanceof Collection) {
                    $target = $target->all();
                } elseif (!is_array($target)) {
                    return Arr::getDefaultValue($target);
                }

                $result = [];
                foreach ($target as $item) {
                    $result[] = self::dataGet($item, $key);
                }

                return in_array('*', $key) ? Arr::collapse($result) : $result;
            }

            if (Arr::accessible($target) && Arr::exists($target, $segment)) {
                $target = $target[$segment];
            } elseif (is_object($target) && isset($target->{$segment})) {
                $target = $target->{$segment};
            } else {
                return Arr::getDefaultValue($default);
            }
        }
        return $target;
    }

    /**
     * 验证集合是否为空
     * @return bool
     */
    public function isEmpty() {
        return empty($this->items);
    }

    /**
     * 判断给定的数据是否是非字符串形式的可执行方法
     * @param $value
     * @return bool
     */
    protected function useAsCallable($value) {
        return !is_string($value) && is_callable($value);
    }

    /**
     * 返回集合items的数组形式
     * @param $items
     * @return array|mixed
     */
    protected function getArrayableItems($items) {
        if (is_array($items)) {
            return $items;
        }
        if ($items instanceof self) {
            return $items->all();
        }
        if ($items instanceof Arrayable) {
            return $items->toArray();
        }
        if ($items instanceof Jsonable) {
            return json_decode($items->toJson(), true);
        }
        if ($items instanceof JsonSerializable) {
            return $items->jsonSerialize();
        }
        if ($items instanceof Traversable) {
            return iterator_to_array($items);
        }
        return (array)$items;

    }

    /**
     * 统计集合元素个数
     * @return int
     */
    public function count() {
        return count($this->items);
    }

    /**
     * 获取集合迭代器
     * @return ArrayIterator|Traversable
     */
    public function getIterator() {
        return new ArrayIterator($this->items);
    }

    /**
     * 判断集合元素是否存在
     * @param mixed $key
     * @return bool
     */
    public function offsetExists($key) {
        return array_key_exists($key, $this->items);
    }

    /**
     * 从集合中删除一个元素
     * @param mixed $key
     */
    public function offsetUnset($key) {
        unset($this->items[$key]);
    }

    /**
     * 往集合中添加元素
     * @param mixed $key
     * @param mixed $value
     */
    public function offsetSet($key, $value) {
        if (is_null($key)) {
            $this->items[] = $value;
        } else {
            $this->items[$key] = $value;
        }
    }

    /**
     * 从集合中获取一个元素
     * @param mixed $key
     * @return mixed
     */
    public function offsetGet($key) {
        return $this->items[$key];
    }

    /**
     * 将对象转换为json字符串
     * @return array|mixed
     */
    public function jsonSerialize() {
        return array_map(function ($value) {
            if ($value instanceof JsonSerializable) {
                return $value->jsonSerialize();
            } elseif ($value instanceof Jsonable) {
                return json_decode($value->toJson(), true);
            } elseif ($value instanceof Arrayable) {
                return $value->toArray();
            }
            return $value;
        }, $this->items);
    }

    /**
     * 将items集合转换为数组
     * @return array|mixed
     */
    public function toArray() {
        return array_map(function ($value) {
            return $value instanceof Arrayable ? $value->toArray() : $value;
        }, $this->items);
    }

    /**
     * 将item集合转换为json字符串
     * @param int $options
     * @return mixed
     */
    public function toJson($options = 0) {
        return json_decode($this->jsonSerialize(), $options);
    }
}